name: "build-test-distribute"
on:
  push:
    branches: ["master", "release-*", "!*-merge-master"]
    tags: ["*"]
  pull_request:
    branches: ["master", "release-*"]
concurrency:
  group: ${{ github.head_ref || github.run_id }}
  cancel-in-progress: true
permissions:
  contents: read
env:
  # This is automatically managed by CI
  K8S_MIN_VERSION: v1.23.17-k3s1
  K8S_MAX_VERSION: v1.29.1-k3s2
  GH_OWNER: ${{ github.repository_owner }}
  KUMA_DIR: "."
  CI_TOOLS_DIR: /home/runner/work/kuma/kuma/.ci_tools
  GH_USER: "github-actions[bot]"
  GH_EMAIL: "<41898282+github-actions[bot]@users.noreply.github.com>"
  GH_REPO: "charts"
jobs:
  check:
    timeout-minutes: 10
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
        with:
          fetch-depth: 0
      - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
        with:
          go-version-file: go.mod
          cache: false
      - uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0
        with:
          path: |
            ${{ env.CI_TOOLS_DIR }}
          key: ${{ runner.os }}-${{ runner.arch }}-devtools-${{ hashFiles('mk/dependencies/deps.lock') }}
          restore-keys: |
            ${{ runner.os }}-${{ runner.arch }}-devtools
      - run: |
          make dev/tools
      - uses: golangci/golangci-lint-action@3cfe3a4abbb849e10058ce4af15d205b6da42804 # v4.0.0
        with:
          args: --fix=false --verbose
          version: v1.56.1
          skip-pkg-cache: true
      - run: |
          make clean
      - run: |
          make check
  test:
    timeout-minutes: 20
    runs-on: ubuntu-latest
    if: ${{ ! contains(github.event.pull_request.labels.*.name, 'ci/skip-test') }}
    steps:
      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
        with:
          fetch-depth: 0
      - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
        with:
          go-version-file: go.mod
      - uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0
        with:
          path: |
            ${{ env.CI_TOOLS_DIR }}
          key: ${{ runner.os }}-${{ runner.arch }}-devtools-${{ hashFiles('mk/dependencies/deps.lock') }}
          restore-keys: |
            ${{ runner.os }}-${{ runner.arch }}-devtools
      - run: |
          make dev/tools
      - run: |
          make test
  distributions:
    timeout-minutes: 40
    needs: ["check", "test", "test_e2e", "test_e2e_env"]
    if: ${{ always() }}
    runs-on: ubuntu-latest
    steps:
      - name: "Halt due to previous failures"
        if: ${{ contains(needs.*.result, 'failure')|| contains(needs.*.result, 'cancelled') }}
        run: |
          exit 1
          # for some reason, GH Action will always trigger a downstream job even if there are errors in an dependent job
          # so we manually check it here. An example could be found here: https://github.com/kumahq/kuma/actions/runs/7044980149
      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
        with:
          fetch-depth: 0
      - name: "Maybe set full matrix"
        if: github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'ci/run-full-matrix')
        id: set-full-matrix-switches
        run: |
          echo 'ENABLED_GOARCHES=arm64 amd64' >> $GITHUB_ENV
          echo 'ENABLED_GOOSES=linux darwin' >> $GITHUB_ENV
      - name: "Add matrix to .run-full-matrix for cache"
        run: |
          echo '${ENABLED_GOARCHES}|${ENABLED_GOOSES}' > .run-full-matrix
      - name: "Maybe set flag to push build artifacts"
        if: github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'ci/force-publish')
        run: |
          echo 'ALLOW_PUSH=true' >> $GITHUB_ENV
      - name: Install dependencies for cross builds
        run: |
          sudo apt-get update; sudo apt-get install -y qemu-user-static binfmt-support
      - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
        with:
          go-version-file: go.mod
          cache-dependency-path: |
            .run-full-matrix
            go.sum
      - uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0
        with:
          path: |
            ${{ env.CI_TOOLS_DIR }}
          key: ${{ runner.os }}-${{ runner.arch }}-devtools-${{ hashFiles('mk/dependencies/deps.lock') }}
          restore-keys: |
            ${{ runner.os }}-${{ runner.arch }}-devtools
      - name: Free up disk space for the Runner
        run: |
          echo "Disk usage before cleanup"
          sudo df -h
          echo "Removing big directories"
          sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc
          echo "Removing images"
          docker system prune --all -f
          echo "Disk usage after cleanup"
          sudo df -h
      - run: |
          make build
      - run: |
          make -j build/distributions
      - run: |
          make -j images
      - run: |
          make -j docker/save
      - name: Run container structure test
        if: ${{ !contains(github.event.pull_request.labels.*.name, 'ci/skip-container-structure-test') && !contains(github.event.pull_request.labels.*.name, 'ci/skip-test') }}
        run: |
          make test/container-structure
      - name: Inspect created tars
        run: |
          for i in build/distributions/out/*.tar.gz; do echo $i; tar -tvf $i; done
      - name: Publish distributions to Pulp
        env:
          PULP_USERNAME: ${{ vars.PULP_USERNAME }}
          PULP_PASSWORD: ${{ secrets.PULP_PASSWORD }}
          CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
        run: |
          make publish/pulp
      - name: Publish images
        env:
          DOCKER_API_KEY: ${{ secrets.DOCKER_API_KEY }}
          DOCKER_USERNAME: ${{ vars.DOCKER_USERNAME }}
        run: |-
          make docker/login
          # ensure we always logout
          function on_exit() {
            make docker/logout
          }
          trap on_exit EXIT
          make docker/push
          make docker/manifest
      - name: package-helm-chart
        id: package-helm
        env:
          HELM_DEV: ${{ !startsWith(github.event.ref, 'refs/tags/') }}
        run: |
          make helm/update-version

          git config user.name "${GH_USER}"
          git config user.email "${GH_EMAIL}"
          git add -u deployments/charts
          # This commit never ends up in the repo
          git commit --allow-empty -m "ci(helm): update versions"
          # To get an idea of what's in the commit to debug
          git show

          make helm/package
          PKG_FILENAME=$(find .cr-release-packages -type f -printf "%f\n")
          echo "filename=${PKG_FILENAME}" >> $GITHUB_OUTPUT
      - name: Upload packaged chart
        uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
        with:
          name: ${{ steps.package-helm.outputs.filename }}
          path: .cr-release-packages/${{ steps.package-helm.outputs.filename }}
          retention-days: ${{ github.event_name == 'pull_request' && 1 || 30 }}
      # Everything from here is only running on releases.
      # Ideally we'd finish the workflow early, but this isn't possible: https://github.com/actions/runner/issues/662
      - name: Generate GitHub app token
        id: github-app-token
        if: ${{ startsWith(github.event.ref, 'refs/tags/') }}
        uses: actions/create-github-app-token@f4c6bf6752984b3a29fcc135a5e70eb792c40c6b # v1.8.0
        with:
          app-id: ${{ secrets.APP_ID }}
          private-key: ${{ secrets.APP_PRIVATE_KEY }}
          owner: ${{ github.repository_owner }}
          repositories: ${{ env.GH_REPO }}
      - name: Release chart
        if: ${{ startsWith(github.event.ref, 'refs/tags/') }}
        env:
          GITHUB_APP: "true"
          GH_TOKEN: ${{ steps.github-app-token.outputs.token }}
        run: make helm/release
  gen_e2e_matrix:
    timeout-minutes: 2
    runs-on: ubuntu-latest
    if: ${{ !contains(github.event.pull_request.labels.*.name, 'ci/skip-test') && !contains(github.event.pull_request.labels.*.name, 'ci/skip-e2e-test') }}
    outputs:
      matrix: ${{ steps.generate-matrix.outputs.matrix }}
    steps:
      - id: generate-matrix
        name: Generate matrix
        env:
          RUN_FULL_MATRIX: ${{ github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'ci/run-full-matrix') }}
          BASE_MATRIX: |-
            {
              "test_e2e": {
                "target": [""],
                "k8sVersion": ["kindIpv6", "${{ env.K8S_MIN_VERSION }}", "${{ env.K8S_MAX_VERSION }}"],
                "arch": ["amd64"],
                "parallelism": [4],
                "cniNetworkPlugin": ["flannel"],
                "sidecarContainers": [""]
              },
              "test_e2e_env": {
                "target": ["kubernetes", "universal", "multizone"],
                "k8sVersion": ["kind", "kindIpv6", "${{ env.K8S_MIN_VERSION }}", "${{ env.K8S_MAX_VERSION }}"],
                "arch": ["amd64"],
                "parallelism": [1],
                "cniNetworkPlugin": ["flannel"],
                "sidecarContainers": [""],
                "exclude":[
                  {"target": "kubernetes", "k8sVersion":"kind"},
                  {"target": "multizone", "k8sVersion":"kind"},
                  {"target":"universal", "k8sVersion":"${{ env.K8S_MIN_VERSION }}"},
                  {"target":"universal", "k8sVersion":"${{ env.K8S_MAX_VERSION }}"}
                ],
                "include":[
                  {"sidecarContainers": "sidecarContainers", "k8sVersion": "${{ env.K8S_MAX_VERSION }}", "target": "kubernetes", "arch": "amd64"},
                  {"k8sVersion": "${{ env.K8S_MAX_VERSION }}", "target": "multizone", "arch": "arm64"},
                  {"k8sVersion": "${{ env.K8S_MAX_VERSION }}", "target": "kubernetes", "arch": "arm64"},
                  {"k8sVersion": "${{ env.K8S_MAX_VERSION }}", "target": "universal", "arch": "arm64"},
                  {"k8sVersion": "${{ env.K8S_MAX_VERSION }}", "target": "gatewayapi", "arch": "amd64"},
                  {"cniNetworkPlugin": "calico", "k8sVersion": "${{ env.K8S_MAX_VERSION }}", "target": "multizone", "arch": "amd64"}
                ]
              }
            }
          # You can modify the include to run one of test suites on PRs (though you'd need to then remove it)
          OVERRIDE_JQ_CMD: |-
            .test_e2e = false
            | .test_e2e_env.include = []
            | .test_e2e_env.exclude += [{"arch": "arm64"}, {"k8sVersion": "kindIpv6"}, {"k8sVersion": "${{ env.K8S_MIN_VERSION}}"}]
        run: |-
          BASE_MATRIX_ALL='${{ env.BASE_MATRIX }}'
          if [[ "${{ env.RUN_FULL_MATRIX }}" != "true" ]]; then
            BASE_MATRIX_ALL=$(echo $BASE_MATRIX_ALL | jq -r '${{ env.OVERRIDE_JQ_CMD }}')
          fi

          echo "final matrix: $BASE_MATRIX_ALL"
          echo "matrix<<EOF" >> $GITHUB_OUTPUT
          echo "$BASE_MATRIX_ALL" >> $GITHUB_OUTPUT
          echo "EOF" >> $GITHUB_OUTPUT
  test_e2e:
    needs: ["gen_e2e_matrix"]
    if: fromJSON(needs.gen_e2e_matrix.outputs.matrix).test_e2e
    strategy:
      matrix: ${{ fromJSON(needs.gen_e2e_matrix.outputs.matrix).test_e2e }}
      fail-fast: false
    uses: ./.github/workflows/e2e.yaml
    with:
      matrix: ${{ toJSON(matrix) }}
    secrets:
      circleCIToken: ${{ secrets.CIRCLECI_TOKEN }}
  test_e2e_env:
    needs: ["gen_e2e_matrix"]
    if: fromJSON(needs.gen_e2e_matrix.outputs.matrix).test_e2e_env
    strategy:
      matrix: ${{ fromJSON(needs.gen_e2e_matrix.outputs.matrix).test_e2e_env }}
      fail-fast: false
    uses: ./.github/workflows/e2e.yaml
    with:
      matrix: ${{ toJSON(matrix) }}
    secrets:
      circleCIToken: ${{ secrets.CIRCLECI_TOKEN }}
